import os
import shutil
import zipfile
import json
import hashlib
from datetime import datetime


class SmartBackup:

    def __init__(self, console, source_dir, backup_dir):
        self.console = console
        self.source_dir = source_dir
        self.backup_dir = backup_dir
        self.metadata_file = os.path.join(backup_dir, "backup_metadata.json")
        self.ensure_directories()

    def printInfo(self, str, message_type="I"):
        print(str)
        self.console.consoleCallback(str, message_type)

    def ensure_directories(self):
        """确保目录存在"""
        os.makedirs(self.backup_dir, exist_ok=True)

    def calculate_md5(self, filepath):
        """计算文件的MD5哈希值"""
        hash_md5 = hashlib.md5()
        try:
            with open(filepath, "rb") as f:
                for chunk in iter(lambda: f.read(4096), b""):
                    hash_md5.update(chunk)
            return hash_md5.hexdigest()
        except Exception as e:
            self.printInfo('计算MD5失败' + filepath + ':' + str(e), "E")
            return None

    def get_file_metadata(self, filepath):
        """获取文件的元数据"""
        try:
            stat = os.stat(filepath)
            return {
                'size': stat.st_size,
                'mtime': stat.st_mtime,
                'md5': self.calculate_md5(filepath)
            }
        except Exception as e:
            self.printInfo('获取文件元数据失败' + filepath + ':' + str(e), "E")
            return None

    def load_metadata(self):
        """加载备份元数据"""
        if os.path.exists(self.metadata_file):
            try:
                with open(self.metadata_file, 'r', encoding='utf-8') as f:
                    return json.load(f)
            except Exception as e:
                self.printInfo("加载元数据失败:" + str(e), "E")
        return {}

    def save_metadata(self, metadata):
        """保存备份元数据"""
        try:
            with open(self.metadata_file, 'w', encoding='utf-8') as f:
                json.dump(metadata, f, ensure_ascii=False, indent=2)
        except Exception as e:
            self.printInfo("保存元数据失败: " + str(e), "E")

    old_metadata = {}
    new_metadata = {}

    def incremental_backup(self, deleteDiff=False):
        """增量备份实现 - 只备份变化的文件"""
        try:
            # 创建本次备份文件夹
            # backup_path = os.path.join(self.backup_dir, f"incremental_{timestamp}")
            # os.makedirs(backup_path, exist_ok=True)
            # 不单独创建文件夹备份，直接使用目录文件夹,因此注释掉上面
            backup_path = self.backup_dir
            startTimestamp = datetime.now()
            # 加载之前的元数据
            old_metadata = self.load_metadata()
            new_metadata = {}
            changed_files = 0
            new_files = 0
            unchanged_files = 0
            taskList = []
            self.printInfo("开始扫描文件变化...", "I")

            # 遍历源文件夹的所有文件
            for root, dirs, files in os.walk(self.source_dir):
                for file in files:
                    source_file = os.path.join(root, file)

                    # 计算相对路径
                    rel_path = os.path.relpath(source_file, self.source_dir)
                    target_file = os.path.join(backup_path, rel_path)

                    # 获取当前文件元数据
                    current_meta = self.get_file_metadata(source_file)
                    if not current_meta:
                        continue

                    new_metadata[rel_path] = current_meta

                    # 检查是否需要备份
                    need_backup = False
                    if rel_path not in old_metadata:
                        # 新文件
                        need_backup = True
                        new_files += 1
                        # self.printInfo("发现新文件: " + str(rel_path), "I")
                    else:
                        # 检查文件是否变化
                        old_meta = old_metadata[rel_path]
                        if (current_meta['size'] != old_meta['size'] or
                                current_meta['mtime'] != old_meta['mtime'] or
                                current_meta['md5'] != old_meta.get('md5')):
                            need_backup = True
                            changed_files += 1
                            self.printInfo("变化文件: " + str(rel_path), "I")
                        else:
                            unchanged_files += 1

                    # 如果需要备份，复制文件
                    if need_backup:
                        self.printInfo("开始复制文件：" + str(rel_path), "I")
                        # 创建目标目录
                        os.makedirs(os.path.dirname(target_file), exist_ok=True)
                        # 复制文件（保留元数据）
                        shutil.copy2(source_file, target_file)
                        # 新增文件后临时保存一次，避免后面失败后文件目录混乱了
                        self.updateMetadata(new_metadata)
                        self.printInfo("完成复制文件：" + str(rel_path), "W")

            deleted_files = set(old_metadata.keys()) - set(new_metadata.keys())
            deleted_files_count = 0
            for deleted_file in deleted_files:
                self.printInfo("源文件夹中已删除文件: " + str(deleted_file), "I")
                if deleteDiff:
                    deleted_files_count += 1
                    os.remove(self.backup_dir + '/' + deleted_file)
                    self.updateMetadata(new_metadata)
                    self.printInfo("备份文件夹同步删除文件: " + str(deleted_file), "W")
            # 输出备份统计
            completeTimestamp = datetime.now()
            self.printInfo("=== 增量备份完成 ===", "I")
            self.printInfo("备份位置: " + str(backup_path), "D")
            self.printInfo("新文件: " + str(new_files) + "个", "D")
            self.printInfo("变化文件: " + str(changed_files) + "个", "D")
            self.printInfo("未变化文件: " + str(unchanged_files) + "个", "D")
            self.printInfo("删除文件: " + str(deleted_files_count) + "个", "D")
            self.printInfo("总文件数: " + str(len(new_metadata)) + "个", "D")
            self.printInfo(
                "备份耗时: " + str(
                    round((completeTimestamp - startTimestamp).total_seconds() / 60, 4)) + "分", "D")
            return backup_path
        except Exception as e:
            self.printInfo("增量备份失败:" + str(e), "E")
            return None

    def updateMetadata(self, temp_metadata):
        self.save_metadata(temp_metadata.copy())

    # deleteDiff是否删除源文件夹不存在的文件
    def backup(self, deleteDiff=False):
        self.incremental_backup(deleteDiff)

    def restore(self, backup_path, target_dir=None):
        """从备份恢复"""
        try:
            if target_dir is None:
                target_dir = self.source_dir + "_restored"

            if backup_path.endswith('.zip'):
                # 解压恢复
                with zipfile.ZipFile(backup_path, 'r') as zipf:
                    zipf.extractall(target_dir)
            else:
                # 文件夹恢复
                shutil.copytree(backup_path, target_dir)

            self.printInfo("恢复完成: " + str(target_dir), "D")
            return target_dir

        except Exception as e:
            self.printInfo("恢复失败: " + str(e), "E")
            return None
